package com.nulldev.util.data.Charsets;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.LinkedList;
import java.util.SortedMap;

public class CharsetUtil {
	public static final Charset UTF_16BE = getCharset0("UTF-16BE");
	public static final Charset UTF_16 = getCharset0("UTF-16");
	public static final Charset UTF_16LE = getCharset0("UTF-16LE");
	public static final Charset US_ASCII = getCharsets("US-ASCII", "US_ASCII");
	public static final Charset ISO_8859_1 = getCharsets("ISO-8859-1", "ISO_8859_1");
	public static final Charset UTF_8 = getCharset0("UTF-8");
	public static final Charset WINDOWS_1252 = getCharset0("windows-1252");

	public static Charset getCharset() {
		if (Charset.isSupported("UTF-8")) {
			return Charset.forName("UTF-8");
		} else if (Charset.isSupported("UTF-16LE")) {
			return Charset.forName("UTF-16LE");
		} else {
			return Charset.defaultCharset();
		}
	}

	private static Charset getCharset0(final String charsetName) {
		if (Charset.isSupported(charsetName)) {
			return Charset.forName(charsetName);
		}
		return Charset.defaultCharset();
	}

	public static Charset getCharsets(final String... charsetNames) {
		for (final String charsetName : charsetNames) {
			if (Charset.isSupported(charsetName)) {
				return Charset.forName(charsetName);
			}
		}
		return Charset.defaultCharset();
	}

	public static Charset getCharset(final String pref) {
		try {
			if (Charset.isSupported(pref)) {
				return Charset.forName(pref);
			} else if (Charset.isSupported("UTF-8")) {
				return Charset.forName("UTF-8");
			} else {
				return Charset.defaultCharset();
			}
		} catch (Exception e) {
			return Charset.defaultCharset();
		}
	}

	public static boolean canHandle(final String charset) {
		return Charset.isSupported(charset);
	}

	public static String convert(String value, String fromEncoding, String toEncoding) {
		return new String(value.getBytes(getCharset(fromEncoding)), getCharset(toEncoding));
	}

	public static String convert(byte[] value, String fromEncoding, String toEncoding) {
		return new String(value, getCharset(toEncoding));
	}

	public static LinkedList<Charset> getCharsets() {
		final SortedMap<String, Charset> charsets = Charset.availableCharsets();
		final LinkedList<Charset> cs = new LinkedList<Charset>();
		for (final Charset c : charsets.values()) {
			if (c != null) {
				cs.add(c);
			}
		}
		return cs;
	}

	public static Charset[] getCharsetsArray() {
		final SortedMap<String, Charset> charsets = Charset.availableCharsets();
		final LinkedList<Charset> cs0 = new LinkedList<Charset>(charsets.values());
		final Charset[] cs = new Charset[charsets.values().size()];
		for (int i = 0; i < charsets.values().size(); i++) {
			cs[i] = cs0.get(i);
		}
		cs0.clear();
		return cs;
	}

	public static Charset charset(final String value) {
		return charset(value, getCharsetsArray());
	}

	public static Charset charset(final String value, final Charset... charsets) {
		String probe = UTF_8.name();
		for (Charset charset : charsets) {
			if (charset != null) {
				if (value.equals(convert(convert(value, charset.name(), probe), probe, charset.name()))) {
					return charset;
				}
			}
		}
		return UTF_8;
	}

	private static final String[] VALS =
		{ UTF_16.name(), UTF_16BE.name(), UTF_16LE.name(), UTF_8.name(), US_ASCII.name(), ISO_8859_1.name() };

	public static Charset detectCharset(final File f) {
		Charset charset = null;

		for (String charsetName : VALS) {
			charset = detectCharset(f, Charset.forName(charsetName));
			if (charset != null) {
				break;
			}
		}

		return charset;
	}

	public static Charset detectCharset(File f, String[] charsets) {
		Charset charset = null;

		for (String charsetName : charsets) {
			charset = detectCharset(f, Charset.forName(charsetName));
			if (charset != null) {
				break;
			}
		}

		return charset;
	}

	private static Charset detectCharset(File f, Charset charset) {
		try {
			BufferedInputStream input = new BufferedInputStream(new FileInputStream(f));

			CharsetDecoder decoder = charset.newDecoder();
			decoder.reset();

			byte[] buffer = new byte[512];
			boolean identified = false;
			while ((input.read(buffer) != -1) && (!identified)) {
				identified = identify(buffer, decoder);
			}

			input.close();

			if (identified) {
				return charset;
			} else {
				return null;
			}

		} catch (Exception e) {
			return Charset.defaultCharset();
		}
	}

	private static boolean identify(byte[] bytes, CharsetDecoder decoder) {
		try {
			decoder.decode(ByteBuffer.wrap(bytes));
		} catch (CharacterCodingException e) {
			return false;
		}
		return true;
	}

	public static Charset def() {
		return Charset.defaultCharset();
	}
}
